package anonymouschat.service;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.net.UnknownHostException;

import anonymouschat.exceptions.ConnectionException;
import anonymouschat.messages.Message;
import anonymouschat.utils.Print;

/**
 * 
 * Holds a socket, input and output streams, and a string of the "local" and
 * "remote" sides
 * 
 */
public class Connection implements Serializable {
	/**
     * 
     */
	private static final long serialVersionUID = 9205752104143856932L;

	private transient Socket socket = null;

	private transient ObjectOutputStream oos = null;

	private transient ObjectInputStream ois = null;

	/**
	 * a representation of the local side of a connection, perhaps a name or an
	 * IP:port
	 */
	private String localEnd = null;
	/**
	 * a representation of the remote side of a connection, perhaps a name or an
	 * IP:port
	 */
	private String remoteEnd = null;

	/**
	 * false until the connection is open successfully
	 */
	private boolean isOpen = false;

	/**
	 * An object to store a socket, in/output streams and when it was last used
	 */
	public Connection() {
		Print.debug("Creating a new connection object");
	}

	/**
	 * Sets information and opens a socket, including output and input streams
	 * 
	 * @param server
	 *            the server to open a connection to
	 * @param localEnd
	 *            the name of the client or server creating the connection
	 * @param remoteEnd
	 *            the name of the destination client or server accepting a
	 *            connection
	 * @throws ConnectionException
	 */
	public Connection(ServerEntry server, String localEnd)
			throws ConnectionException {
		this();
		this.openCom(server);
		this.localEnd = localEnd;
	}

	/**
	 * 
	 * @param socketToOpen
	 * @param localEnd
	 * @param remoteEnd
	 * @throws ConnectionException
	 */
	public Connection(Socket socketToOpen, String localEnd, String remoteEnd)
			throws ConnectionException {
		this();
		this.openCom(socketToOpen, remoteEnd);
		this.localEnd = localEnd;
	}

	/**
	 * 
	 * @param localEnd
	 *            a string representation of the local end of this connection
	 */
	public Connection(String localEnd) {
		this();
		this.localEnd = localEnd;
	}

	/**
	 * close the in/out streams and socket, set all to null
	 */
	public void closeCom() {
		try {
			oos.close();
			ois.close();
			socket.close();
			oos = null;
			ois = null;
			socket = null;
		} catch (IOException e) {

		}
		isOpen = false;
	}

	/**
	 * A representation of the connection <br>
	 * (local end) &lt -- &gt (remote end)
	 * 
	 * @return the string representation of the local and remote ends of the
	 *         connection
	 */
	public String getInfo() {
		return localEnd + "<-->" + remoteEnd;
	}

	/**
	 * @return the ObjectInputStream for this connection
	 */
	public ObjectInputStream getInputStream() {
		return ois;
	}

	/**
	 * @return the string representing the local side of this connection
	 */
	public String getLocalEnd() {
		return localEnd;
	}

	/**
	 * @return the ObjectOutputStream for this connection
	 */
	public ObjectOutputStream getOutputStream() {
		return oos;
	}

	/**
	 * @return the string representing the remote side of this connection
	 */
	public String getRemoteEnd() {
		return remoteEnd;
	}

	/**
	 * @return the socket for this connection
	 */
	public Socket getSocket() {
		return socket;
	}

	/**
	 * @return <b>true</b> if the socket is open and connected
	 */
	public boolean isOpen() {
		return isOpen;
	}

	/**
	 * 
	 * @param server
	 *            a server to open a connection with
	 * @throws ConnectionException
	 */
	private void openCom(ServerEntry server) throws ConnectionException {
		try {
			this.openCom(new Socket(server.serverIP, server.serverPort),
					server.serverName);
		} catch (UnknownHostException e) {
			throw new ConnectionException("UnknownHostException");
		} catch (IOException e) {
			throw new ConnectionException("IOException");
		}

	}

	/**
	 * 
	 * @param socketToOpen
	 *            a socket to use to create an in/out stream
	 * @throws ConnectionException
	 */
	public void openCom(Socket socketToOpen) throws ConnectionException {
		this.openCom(socketToOpen, socketToOpen.getInetAddress().toString()
				+ ":" + socketToOpen.getPort());
	}

	/**
	 * 
	 * @param newSocket
	 *            a socket to create the streams of
	 * @throws ConnectionException
	 */
	public void openCom(Socket newSocket, String remoteEnd)
			throws ConnectionException {
		try {
			this.socket = newSocket; // new Socket( server.serverIP,
										// server.serverPort );
			this.socket.setKeepAlive(true);
			oos = new ObjectOutputStream(socket.getOutputStream());
			ois = new ObjectInputStream(socket.getInputStream());
			this.remoteEnd = remoteEnd;
		} catch (IOException e) {
			throw new ConnectionException("IOException");
		}
		isOpen = true;
	}

	/**
	 * Send message using this OutConnection's Socket
	 * 
	 * @param message
	 *            - message to send
	 * @throws IOException
	 */
	public void sendMessage(Serializable message) throws IOException {
		Print.sending(this.localEnd,
				" serializable message " + message.toString() + " to "
						+ this.remoteEnd);
		Print.debug((Message) message);
		this.oos.writeObject(message);
		this.oos.flush();
	}

	/**
	 * @param localEnd
	 *            set the string representing the local side of this connection
	 */
	public void setLocalEnd(String localEnd) {
		this.localEnd = localEnd;
	}

	/**
	 * @param remoteEnd
	 *            set the string representing the remote side of this connection
	 */
	public void setRemoteEnd(String remoteEnd) {
		this.remoteEnd = remoteEnd;
	}

}
